import os
import asyncio
import threading
import flet as ft
from flet import (
    Page, 
    Text, 
    TextField, 
    ElevatedButton, 
    Row, 
    Column, 
    Container, 
    ProgressBar,
    Image,
    Dropdown,
    dropdown,
    IconButton,
    Divider,
    Card,
    SnackBar,
    Banner,
    border_radius,
    padding,
    margin,
    alignment,
    animation,
    BoxShadow,
    Icon,
    AlertDialog,
    TextButton,
    Stack
)
import tempfile
import requests
from io import BytesIO
from PIL import Image as PILImage
import json

from utils import DownloaderFactory, VideoInfo

class VideoDownloaderApp:
    def __init__(self):
        self.downloader_factory = DownloaderFactory()
        self.current_video_info = None
        self.current_url = None
        
        # 下载路径设置
        self.settings_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "settings.json")
        self.tasks_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "tasks.json")
        self.load_settings()
        self.load_tasks()
        
        # 确保下载目录存在
        os.makedirs(self.download_dir, exist_ok=True)
        
        # 下载队列管理
        self.download_queue = []  # 存储待下载任务
        self.active_downloads = {}  # 存储正在下载的任务 {task_id: thread}
        self.download_tasks = {}  # 存储所有任务信息 {task_id: task_info}
        self.max_concurrent_downloads = 3  # 最大并行下载数
        self.next_task_id = 1  # 任务ID计数器
        self.queue_lock = threading.Lock()  # 队列操作锁
        
        # 抖音 cookies 文件路径
        self.douyin_cookies_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "douyin_cookies.txt")
        # 检查是否存在 cookies 文件
        if not os.path.exists(self.douyin_cookies_file):
            # 创建空的 cookies 文件
            with open(self.douyin_cookies_file, "w", encoding="utf-8") as f:
                f.write("# 抖音 cookies 文件，请将抖音网页版的 cookies 粘贴到这里\n")
                f.write("# 格式: domain_name\tFALSE\tpath\tFALSE\texpiry_epoch\tname\tvalue\n")
        
        # 页面管理
        self.pages = {}
        self.current_page = None
    
    def main(self, page: Page):
        page.title = "多平台视频下载器"
        page.theme_mode = ft.ThemeMode.LIGHT  # 固定使用亮色主题
        page.padding = 0  # 移除页面内边距
        page.window_width = 800
        page.window_height = 700
        page.window_min_width = 600
        page.window_min_height = 500
        page.bgcolor = ft.colors.WHITE  # 设置白色背景
        
        # 保存page引用
        self.page = page
        
        # 创建页面
        self.pages = {
            "home": self.create_home_page(),
            "downloads": self.create_downloads_page(),
            "settings": self.create_settings_page()
        }
        
        # 设置初始页面
        self.current_page = "home"
        page.add(self.pages[self.current_page])
    
    def create_home_page(self):
        """创建主页面"""
        # 创建错误提示横幅
        self.error_banner = Banner(
            bgcolor=ft.colors.RED_100,
            leading=IconButton(icon=ft.icons.ERROR_OUTLINE, icon_color=ft.colors.RED),
            content=Text("", color=ft.colors.RED_700, size=14),
            actions=[
                ft.TextButton("关闭", on_click=lambda e: self.hide_error_banner(e)),
            ],
            visible=False,
        )
        
        # 创建成功提示
        self.success_snack = SnackBar(
            content=Text("", size=14),
            bgcolor=ft.colors.GREEN_700,
            action="确定",
            action_color=ft.colors.WHITE,
        )
        
        # 创建URL输入框
        self.url_input = TextField(
            label="视频链接",
            hint_text="粘贴视频链接...",
            prefix_icon=ft.icons.LINK,
            expand=True,
            border_radius=8,
            focused_border_color=ft.colors.BLUE_700,
            focused_border_width=2,
            text_size=14,
        )
        
        # 创建解析按钮
        self.parse_button = ElevatedButton(
            text="解析",
            style=ft.ButtonStyle(
                bgcolor=ft.colors.BLUE_700,
                color=ft.colors.WHITE,
                shape=ft.RoundedRectangleBorder(radius=8),
            ),
            height=45,
            width=120,
            on_click=self.parse_url,
        )
        
        # 创建清晰度选择下拉框
        self.format_dropdown = Dropdown(
            label="选择清晰度",
            hint_text="请先解析视频",
            disabled=True,
            expand=True,
            border_radius=8,
            focused_border_color=ft.colors.BLUE_700,
            focused_border_width=2,
            text_size=14,
        )
        
        # 创建下载按钮
        self.download_button = ElevatedButton(
            text="下载",
            disabled=True,
            on_click=self.download_video,
            style=ft.ButtonStyle(
                bgcolor=ft.colors.GREEN_700,
                color=ft.colors.WHITE,
                elevation=4,
                shadow_color=ft.colors.with_opacity(0.3, ft.colors.GREEN_700),
                shape=ft.RoundedRectangleBorder(radius=10),
                animation_duration=300,
                overlay_color=ft.colors.with_opacity(0.2, ft.colors.WHITE),
            ),
            height=45,
            width=120,
        )
        
        # 创建打开下载文件夹按钮
        self.open_folder_button = ElevatedButton(
            text="打开下载文件夹",
            on_click=self.open_download_folder,
            style=ft.ButtonStyle(
                bgcolor=ft.colors.BLUE_GREY_700,
                color=ft.colors.WHITE,
                elevation=4,
                shadow_color=ft.colors.with_opacity(0.3, ft.colors.BLUE_GREY_700),
                shape=ft.RoundedRectangleBorder(radius=10),
                animation_duration=300,
                overlay_color=ft.colors.with_opacity(0.2, ft.colors.WHITE),
            ),
            height=45,
            width=120,
        )
        
        # 创建视频信息容器（替换原来的卡片）
        self.video_info_card = Container(
            content=Column([
                Text("请先解析视频", size=18, weight="bold"),
                Row([
                    # 左侧视频封面
                    Container(
                        height=200, 
                        width=350,
                        content=Image(
                            src="",
                            fit=ft.ImageFit.CONTAIN,
                            repeat=ft.ImageRepeat.NO_REPEAT,
                        ), 
                        visible=False,
                        border_radius=10,
                        bgcolor=ft.colors.with_opacity(0.05, ft.colors.BLACK),
                        margin=margin.only(top=10, right=15),
                    ),
                    # 右侧视频信息和操作区
                    Container(
                        content=Column([
                            # 清晰度选择下拉框
                            Container(
                                content=self.format_dropdown,
                                margin=margin.only(bottom=10),
                                width=200,  # 设置固定宽度
                            ),
                            # 下载按钮
                            Container(
                                content=self.download_button,
                                margin=margin.only(bottom=10),
                                width=200,  # 设置固定宽度
                            ),
                            # 打开下载文件夹按钮
                            Container(
                                content=self.open_folder_button,
                                width=200,  # 设置固定宽度
                            ),
                        ], 
                        alignment=ft.MainAxisAlignment.END,  # 右对齐
                        spacing=10,
                        ),
                        visible=False,
                        expand=True,
                        margin=margin.only(top=10),
                    ),
                ], alignment=ft.MainAxisAlignment.START),
            ], spacing=10),  # 添加垂直间距
            padding=20,
            border_radius=10,
            bgcolor=ft.colors.with_opacity(0.05, ft.colors.BLACK),
            visible=False,
            margin=margin.only(top=10, bottom=10),
        )
        
        # 创建进度条
        self.progress_bar = ProgressBar(
            visible=False,
            color=ft.colors.BLUE_700,
            bgcolor=ft.colors.BLUE_100,
            height=8,
            border_radius=4,
        )
        self.progress_text = Text("", visible=False, size=14, color=ft.colors.BLUE_700)
        
        # 创建抖音 cookies 设置按钮
        self.douyin_cookies_button = IconButton(
            icon=ft.icons.SETTINGS,
            icon_color=ft.colors.BLUE_700,
            tooltip="设置抖音 cookies",
            on_click=lambda e: self.navigate("settings"),
        )
        
        # 创建下载列表按钮
        downloads_button = IconButton(
            icon=ft.icons.DOWNLOAD_ROUNDED,
            icon_color=ft.colors.BLUE_700,
            tooltip="下载列表",
            on_click=lambda e: self.navigate("downloads"),
        )
        
        # 创建设置按钮
        settings_button = IconButton(
            icon=ft.icons.SETTINGS,
            icon_color=ft.colors.BLUE_700,
            tooltip="设置",
            on_click=lambda e: self.navigate("settings"),
        )
        
        # 创建主要内容区域
        main_content = Container(
            content=Column([
                # 添加标题和按钮
                Row([
                    Container(
                        content=Text("多平台视频下载器", size=24, weight="bold", color=ft.colors.BLUE_700),
                        margin=margin.only(bottom=5),
                        expand=True,
                        alignment=alignment.center,
                    ),
                    downloads_button,
                    settings_button,
                ], alignment=ft.MainAxisAlignment.SPACE_BETWEEN),
                
                # 简化的平台标签行
                Container(
                    content=Row([
                        Container(
                            content=Text("支持平台:", size=12),
                            margin=margin.only(right=5),
                        ),
                        Container(
                            content=Text("抖音", color=ft.colors.PINK_500, size=12, weight="bold"),
                            margin=margin.only(right=8),
                        ),
                        Container(
                            content=Text("YouTube", color=ft.colors.RED_600, size=12, weight="bold"),
                            margin=margin.only(right=8),
                        ),
                        Container(
                            content=Text("快手", color=ft.colors.ORANGE_500, size=12, weight="bold"),
                            margin=margin.only(right=8),
                        ),
                        Container(
                            content=Text("小红书", color=ft.colors.RED_700, size=12, weight="bold"),
                            margin=margin.only(right=8),
                        ),
                        Container(
                            content=Text("西瓜视频", color=ft.colors.DEEP_ORANGE_500, size=12, weight="bold"),
                            margin=margin.only(right=8),
                        ),
                        Container(
                            content=Text("Bilibili", color=ft.colors.PINK_700, size=12, weight="bold"),
                            margin=margin.only(right=8),
                        ),
                        Container(
                            content=Text("今日头条", color=ft.colors.BLUE_700, size=12, weight="bold"),
                        ),
                    ], wrap=True),
                    margin=margin.only(bottom=15),
                    alignment=alignment.center,
                ),
                
                Container(
                    content=Row([self.url_input, self.parse_button], spacing=10),
                    margin=margin.only(bottom=10),
                ),
                self.video_info_card,
                Column([self.progress_bar, self.progress_text], spacing=5),
            ]),
            padding=32,  # 修改内边距为32
            bgcolor=ft.colors.WHITE,
        )
        
        # 组装页面
        return Column([
            self.error_banner,
            main_content,
        ])
    
    def create_downloads_page(self):
        """创建下载列表页面"""
        # 创建返回按钮
        back_button = IconButton(
            icon=ft.icons.ARROW_BACK,
            icon_color=ft.colors.BLUE_700,
            tooltip="返回主页",
            on_click=lambda e: self.navigate("home"),
        )
        
        # 创建下载队列列表
        self.download_queue_list = Column(
            spacing=10,
            scroll=ft.ScrollMode.AUTO,
            expand=True,
        )
        
        # 创建空状态提示
        self.empty_state = Container(
            content=Column([
                Text(
                    "暂无下载任务",
                    size=16,
                    weight="bold",
                    color=ft.colors.BLUE_GREY_400,
                ),
                Text(
                    "返回主页添加视频下载任务",
                    size=14,
                    color=ft.colors.BLUE_GREY_300,
                ),
                Container(
                    content=ElevatedButton(
                        text="返回主页",
                        icon=ft.icons.HOME,
                        on_click=lambda e: self.navigate("home"),
                        style=ft.ButtonStyle(
                            bgcolor=ft.colors.BLUE_700,
                            color=ft.colors.WHITE,
                            elevation=0,
                            animation_duration=300,
                        ),
                    ),
                    margin=margin.only(top=20),
                ),
            ], 
            horizontal_alignment=ft.CrossAxisAlignment.CENTER,
            spacing=5,
            ),
            alignment=ft.alignment.center,
            expand=True,
        )
        
        # 创建页面内容
        content = Container(
            content=Column([
                # 标题栏
                Row([
                    back_button,
                    Container(
                        content=Text("下载列表", size=24, weight="bold", color=ft.colors.BLUE_700),
                        margin=margin.only(bottom=5),
                        expand=True,
                        alignment=alignment.center,
                    ),
                ], alignment=ft.MainAxisAlignment.START),
                
                # 下载列表容器
                Container(
                    content=Stack([
                        self.download_queue_list,
                        self.empty_state,
                    ]),
                    bgcolor=ft.colors.WHITE,
                    padding=32,  # 增加内边距
                    expand=True,
                ),
            ]),
            padding=32,  # 增加内边距
            expand=True,
        )
        
        return content
    
    def create_settings_page(self):
        """创建设置页面"""
        # 创建返回按钮
        back_button = IconButton(
            icon=ft.icons.ARROW_BACK,
            icon_color=ft.colors.BLUE_700,
            tooltip="返回主页",
            on_click=lambda e: self.navigate("home"),
        )
        
        # 读取当前 cookies 内容
        cookies_content = ""
        try:
            with open(self.douyin_cookies_file, "r", encoding="utf-8") as f:
                cookies_content = f.read()
        except Exception as e:
            print(f"读取cookies文件出错: {str(e)}")
        
        # 创建抖音设置区域
        douyin_settings = Container(
            content=Column([
                Text("抖音设置", size=18, weight="bold"),
                Text("请按照以下步骤设置抖音cookies：", size=14),
                Text("1. 使用浏览器打开抖音网页版并登录", size=14),
                Text("2. 按F12打开开发者工具，找到Network", size=14),
                Text("3. 复制cookies内容，粘贴到下方", size=14),
                TextField(
                    value=cookies_content,
                    multiline=True,
                    min_lines=5,
                    max_lines=8,
                    label="抖音 Cookies",
                    hint_text="在此粘贴cookies...",
                    on_change=self.save_cookies_from_settings,
                ),
            ], spacing=10),
            padding=20,
            border_radius=8,
            bgcolor=ft.colors.with_opacity(0.05, ft.colors.BLACK),
        )
        
        # 创建下载路径设置区域
        download_path_settings = Container(
            content=Column([
                Text("下载设置", size=18, weight="bold"),
                Row([
                    TextField(
                        value=self.download_dir,
                        label="下载保存路径",
                        expand=True,
                        read_only=True,
                        border_radius=8,
                    ),
                    ElevatedButton(
                        "选择路径",
                        icon=ft.icons.FOLDER_OPEN,
                        on_click=self.pick_download_dir,
                        style=ft.ButtonStyle(
                            bgcolor=ft.colors.BLUE_700,
                            color=ft.colors.WHITE,
                        ),
                    ),
                ], spacing=10),
            ], spacing=10),
            padding=20,
            border_radius=8,
            bgcolor=ft.colors.with_opacity(0.05, ft.colors.BLACK),
        )
        
        # 创建页面内容
        content = Container(
            content=Column([
                # 标题栏
                Container(
                    content=Row([
                        back_button,
                        Container(
                            content=Text("设置", size=24, weight="bold", color=ft.colors.BLUE_700),
                            margin=margin.only(bottom=5),
                            expand=True,
                            alignment=alignment.center,
                        ),
                    ], alignment=ft.MainAxisAlignment.START),
                    padding=16,
                ),
                
                # 设置内容区域（添加滚动功能）
                Container(
                    content=Column(
                        [
                            download_path_settings,
                            douyin_settings,
                        ],
                        spacing=20,
                        scroll=ft.ScrollMode.AUTO,
                    ),
                    expand=True,
                    padding=ft.padding.symmetric(horizontal=16),
                ),
            ]),
            expand=True,
        )
        
        return content

    def save_cookies_from_settings(self, e):
        """从设置页面保存抖音 cookies"""
        try:
            cookies_content = e.control.value
            if not cookies_content or not cookies_content.strip():
                self.show_error("请输入cookies内容")
                return
            
            # 保存cookies
            with open(self.douyin_cookies_file, "w", encoding="utf-8") as f:
                f.write(cookies_content)
            
            # 显示成功提示
            self.success_snack.content.value = "抖音 cookies 已保存"
            self.success_snack.open = True
            self.page.update()
            
        except Exception as e:
            self.show_error(f"保存 cookies 失败: {str(e)}")
    
    def navigate(self, page_name):
        """页面导航"""
        if page_name == self.current_page:
            return
        
        # 清除当前页面
        self.page.controls.clear()
        
        # 设置新页面
        self.current_page = page_name
        self.page.add(self.pages[page_name])
        
        # 更新UI
        self.page.update()
    
    def hide_error_banner(self, e):
        """隐藏错误横幅"""
        self.error_banner.visible = False
        self.page.update()
    
    def load_thumbnail_sync(self, url):
        """同步加载视频缩略图"""
        try:
            response = requests.get(url)
            if response.status_code == 200:
                # 保存图片到临时文件
                with tempfile.NamedTemporaryFile(delete=False, suffix='.jpg') as temp_file:
                    temp_file.write(response.content)
                    temp_path = temp_file.name
                
                # 调整图片大小
                img = PILImage.open(temp_path)
                width, height = img.size
                max_width = 350
                if width > max_width:
                    ratio = max_width / width
                    new_height = int(height * ratio)
                    img = img.resize((max_width, new_height), PILImage.LANCZOS)
                    img.save(temp_path)
                
                # 安全地更新UI
                self.video_info_card.content.controls[1].controls[0].content.src = temp_path
                self.video_info_card.content.controls[1].controls[0].visible = True
                self.video_info_card.content.controls[1].controls[1].visible = True
                self.page.update()
        except Exception as e:
            print(f"加载缩略图时出错: {str(e)}")
    
    def parse_url(self, e):
        """解析视频URL"""
        url = self.url_input.value.strip()
        if not url:
            # 显示空链接提示对话框
            self.show_error_dialog("提示", "请从网页端复制正确的视频链接到输入框中")
            return
        
        self.current_url = url
        # 更新解析按钮状态
        self.parse_button.disabled = True
        self.parse_button.text = "解析中..."
        # 禁用URL输入框
        self.url_input.disabled = True
        self.error_banner.visible = False
        self.format_dropdown.options = []
        self.format_dropdown.disabled = True
        self.download_button.disabled = True
        self.video_info_card.visible = False
        self.page.update()
        
        # 使用线程来处理异步任务
        threading.Thread(target=self.parse_url_thread, args=(url,), daemon=True).start()
    
    def parse_url_thread(self, url):
        """在线程中解析URL"""
        try:
            # 获取合适的下载器
            downloader = self.downloader_factory.get_downloader(url)
            if not downloader:
                self.safe_show_error("不支持的视频链接，目前仅支持抖音、快手等平台")
                self.safe_reset_parse_button()
                return
            
            # 如果是抖音链接，检查是否有 cookies
            if "douyin.com" in url or "iesdouyin.com" in url:
                if not os.path.exists(self.douyin_cookies_file):
                    self.safe_show_error("抖音视频解析需要 cookies，请点击右上角设置按钮配置")
                    self.safe_reset_parse_button()
                    return
                
                # 检查 cookies 文件大小
                if os.path.getsize(self.douyin_cookies_file) < 50:
                    self.safe_show_error("抖音 cookies 内容无效，请重新设置正确的 cookies")
                    self.safe_reset_parse_button()
                    return
                
                try:
                    # 设置 cookies
                    downloader.set_cookies_file(self.douyin_cookies_file)
                except Exception as e:
                    self.safe_show_error(f"cookies 格式错误: {str(e)}\n请确保按照正确格式设置 cookies")
                    self.safe_reset_parse_button()
                    return
            
            # 创建事件循环
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            
            try:
                # 提取视频信息
                video_info = loop.run_until_complete(downloader.extract_video_info(url))
                if not video_info:
                    self.safe_show_error("无法解析视频信息，请检查链接是否正确或重新设置cookies")
                    self.safe_reset_parse_button()
                    return
                
                self.current_video_info = video_info
                
                # 更新视频信息容器
                self.video_info_card.content.controls[0].value = video_info["title"]
                
                # 更新格式下拉框
                options = []
                for fmt in video_info["formats"]:
                    height = fmt.get('height', 0)
                    width = fmt.get('width', 0)
                    format_id = fmt.get('format_id', '')
                    format_note = fmt.get('format_note', '')
                    ext = fmt.get('ext', 'mp4')
                    
                    if height and width:
                        label = f"{width}x{height} ({format_note})" if format_note else f"{width}x{height}"
                    else:
                        label = format_note if format_note else "未知清晰度"
                    
                    options.append(dropdown.Option(key=format_id, text=f"{label} - {ext}"))
                
                self.format_dropdown.options = options
                self.format_dropdown.disabled = False
                self.format_dropdown.value = options[0].key if options else None
                self.download_button.disabled = False
                
                # 显示所有组件
                self.video_info_card.visible = True
                self.video_info_card.content.controls[1].controls[0].visible = True  # 显示封面容器
                self.video_info_card.content.controls[1].controls[1].visible = True  # 显示操作区
                
                # 重置解析按钮
                self.parse_button.disabled = False
                self.parse_button.text = "解析"
                # 恢复URL输入框可用状态
                self.url_input.disabled = False
                
                # 安全地在主线程中更新UI
                self.page.update()
                
                # 加载缩略图
                if video_info["cover_url"] and video_info["cover_url"].startswith("http"):
                    self.load_thumbnail_sync(video_info["cover_url"])
                else:
                    print("无效的封面 URL，无法加载缩略图")
            
            except Exception as e:
                error_msg = str(e)
                if "Video unavailable" in error_msg:
                    self.safe_show_error("视频不可用或已被删除")
                elif "Sign in to confirm your age" in error_msg:
                    self.safe_show_error("需要登录才能观看此视频")
                elif "Unable to extract uploader id" in error_msg:
                    self.safe_show_error("无法获取视频信息，请检查链接是否正确")
                else:
                    self.safe_show_error(f"解析YouTube视频失败: {error_msg}")
                self.safe_reset_parse_button()
                return
        
        except Exception as e:
            import traceback
            print(f"解析过程中出错: {str(e)}")
            traceback.print_exc()
            self.safe_show_error(f"解析过程中出错: {str(e)}")
            self.safe_reset_parse_button()
    
    def safe_reset_parse_button(self):
        """安全地重置解析按钮状态"""
        self.parse_button.disabled = False
        self.parse_button.text = "解析"
        # 恢复URL输入框可用状态
        self.url_input.disabled = False
        self.page.update()
    
    def reset_parse_button(self, e):
        """重置解析按钮状态"""
        self.parse_button.disabled = False
        self.parse_button.text = "解析"
        # 恢复URL输入框可用状态
        self.url_input.disabled = False
        self.page.update()
    
    def download_video(self, e):
        """将视频添加到下载队列"""
        if not self.current_video_info or not self.current_url:
            self.show_error("请先解析视频")
            return
        
        format_id = self.format_dropdown.value
        if not format_id:
            self.show_error("请选择清晰度")
            return
        
        # 准备任务信息
        selected_format = None
        for fmt in self.current_video_info["formats"]:
            if fmt.get('format_id') == format_id:
                selected_format = fmt
                break
        
        if not selected_format:
            self.show_error("无法找到所选格式")
            return
        
        # 创建任务信息
        task_id = self.next_task_id
        self.next_task_id += 1
        
        task_info = {
            "id": task_id,
            "url": self.current_url,
            "format_id": format_id,
            "title": self.current_video_info["title"],
            "thumbnail": self.current_video_info["cover_url"],
            "status": "queued",  # queued, downloading, completed, failed, cancelled
            "progress": 0,
            "format_note": selected_format.get('format_note', ''),
            "resolution": f"{selected_format.get('width', '')}x{selected_format.get('height', '')}",
            "ext": selected_format.get('ext', 'mp4'),
        }
        
        # 添加到任务列表
        with self.queue_lock:
            self.download_tasks[task_id] = task_info
            self.download_queue.append(task_id)
        
        # 显示下载队列
        self.download_queue_list.visible = True
        
        # 更新UI
        self.add_task_to_ui(task_info)
        
        # 重置下载按钮
        self.download_button.disabled = False
        self.download_button.text = "下载"
        self.page.update()
        
        # 启动队列处理
        self.process_download_queue()
    
    def safe_reset_download_button(self):
        """安全地重置下载按钮状态"""
        def update_button():
            self.download_button.disabled = False
            self.download_button.text = "下载"
        
        self.page.add_async_callback(update_button)
    
    def reset_download_button(self, e):
        """重置下载按钮状态"""
        self.download_button.disabled = False
        self.download_button.text = "下载"
        self.page.update()
    
    def safe_update_progress(self, progress):
        """安全地更新下载进度"""
        self.progress_bar.value = progress
        self.progress_text.value = f"下载中... {int(progress * 100)}%"
        self.page.update()
    
    def update_progress(self, progress):
        """更新下载进度"""
        self.progress_bar.value = progress
        self.progress_text.value = f"下载中... {int(progress * 100)}%"
        self.page.update()
    
    def safe_show_error(self, message):
        """安全地显示错误信息"""
        self.error_banner.content.value = message
        self.error_banner.visible = True
        self.progress_bar.visible = False
        self.progress_text.visible = False
        self.page.update()
    
    def show_error(self, message):
        """显示错误信息"""
        self.error_banner.content.value = message
        self.error_banner.visible = True
        self.progress_bar.visible = False
        self.progress_text.visible = False
        self.page.update()
    
    def open_download_folder(self, e):
        """打开下载文件夹"""
        import subprocess
        import platform
        
        system = platform.system()
        try:
            if system == "Windows":
                os.startfile(self.download_dir)
            elif system == "Darwin":  # macOS
                subprocess.run(["open", self.download_dir])
            else:  # Linux
                subprocess.run(["xdg-open", self.download_dir])
        except Exception as e:
            self.show_error(f"无法打开下载文件夹: {str(e)}")
    
    def platform_hover(self, e):
        """平台卡片悬停效果"""
        e.control.scale = 1.05 if e.data == "true" else 1.0
        e.control.update()
    
    def toggle_theme(self, e):
        """切换主题"""
        if self.page.theme_mode == ft.ThemeMode.LIGHT:
            self.page.theme_mode = ft.ThemeMode.DARK
            e.control.icon = ft.icons.LIGHT_MODE
        else:
            self.page.theme_mode = ft.ThemeMode.LIGHT
            e.control.icon = ft.icons.DARK_MODE
        self.page.update()
        
    def add_task_to_ui(self, task_info):
        """将任务添加到UI队列中"""
        # 创建任务容器
        task_container = Container(
            content=Column([
                Row([
                    # 任务标题和状态
                    Column([
                        Text(
                            task_info["title"], 
                            size=14, 
                            weight="bold",
                            no_wrap=True,
                            overflow=ft.TextOverflow.ELLIPSIS,
                            width=500,
                        ),
                        Text(
                            f"{task_info['resolution']} ({task_info['format_note']}) - {task_info['ext']}",
                            size=12,
                            color=ft.colors.GREY_700,
                        ),
                    ], spacing=2, expand=True),
                    
                    # 控制按钮区域
                    Row([
                        # 暂停/恢复按钮
                        IconButton(
                            icon=ft.icons.PAUSE if task_info["status"] in ["queued", "downloading"] else ft.icons.PLAY_ARROW,
                            icon_color=ft.colors.BLUE_700,
                            tooltip="暂停" if task_info["status"] in ["queued", "downloading"] else "继续",
                            on_click=lambda e, task_id=task_info["id"]: self.toggle_download(task_id),
                            visible=task_info["status"] in ["queued", "downloading", "paused"],
                        ),
                        # 取消按钮
                        IconButton(
                            icon=ft.icons.CANCEL,
                            icon_color=ft.colors.RED_500,
                            tooltip="取消下载",
                            on_click=lambda e, task_id=task_info["id"]: self.cancel_download(task_id),
                            visible=task_info["status"] in ["queued", "downloading", "paused"],
                        ),
                    ], spacing=0),
                ]),
                
                # 进度条
                Container(
                    content=Column([
                        ProgressBar(
                            value=task_info["progress"],
                            bgcolor=ft.colors.BLUE_100,
                            color=ft.colors.BLUE_500,
                            height=5,
                            border_radius=5,
                        ),
                        Row([
                            Text(
                                self.get_status_text(task_info["status"]),
                                size=12,
                                color=self.get_status_color(task_info["status"]),
                            ),
                            Text(
                                f"{int(task_info['progress'] * 100)}%",
                                size=12,
                                color=ft.colors.BLUE_700,
                                visible=task_info["status"] == "downloading",
                            ),
                        ], alignment=ft.MainAxisAlignment.SPACE_BETWEEN),
                    ], spacing=2),
                    margin=margin.only(top=5),
                ),
            ]),
            padding=10,
            margin=margin.only(bottom=5),
            border_radius=8,
            bgcolor=ft.colors.with_opacity(0.05, ft.colors.BLACK),
        )
        
        # 保存任务容器引用
        task_info["card"] = task_container
        
        # 添加到UI并隐藏空状态提示
        self.empty_state.visible = False
        self.download_queue_list.controls.append(task_container)
        self.page.update()
    
    def get_status_text(self, status):
        """获取状态文本"""
        status_map = {
            "queued": "排队中",
            "downloading": "下载中",
            "completed": "已完成",
            "failed": "下载失败",
            "cancelled": "已取消",
            "paused": "已暂停",
        }
        return status_map.get(status, status)
    
    def get_status_color(self, status):
        """获取状态颜色"""
        status_color = {
            "queued": ft.colors.GREY_700,
            "downloading": ft.colors.BLUE_700,
            "completed": ft.colors.GREEN_700,
            "failed": ft.colors.RED_700,
            "cancelled": ft.colors.ORANGE_700,
            "paused": ft.colors.ORANGE_500,
        }
        return status_color.get(status, ft.colors.GREY_700)
    
    def toggle_download(self, task_id):
        """切换下载状态（暂停/恢复）"""
        with self.queue_lock:
            if task_id not in self.download_tasks:
                return
            
            task_info = self.download_tasks[task_id]
            current_status = task_info["status"]
            
            if current_status == "paused":
                # 恢复下载
                if task_id not in self.download_queue:
                    self.download_queue.append(task_id)
                task_info["status"] = "queued"
                self.update_task_ui(task_info)
                self.process_download_queue()
            
            elif current_status in ["queued", "downloading"]:
                # 暂停下载
                if task_id in self.download_queue:
                    self.download_queue.remove(task_id)
                if task_id in self.active_downloads:
                    task_info["status"] = "paused"
                    self.update_task_ui(task_info)
    
    def update_task_ui(self, task_info):
        """更新任务UI"""
        if "card" not in task_info:
            return
        
        container = task_info["card"]
        column = container.content
        
        # 更新进度条
        progress_container = column.controls[1]
        progress_bar = progress_container.content.controls[0]
        progress_bar.value = task_info["progress"]
        
        # 更新状态文本
        status_row = progress_container.content.controls[1]
        status_text = status_row.controls[0]
        status_text.value = self.get_status_text(task_info["status"])
        status_text.color = self.get_status_color(task_info["status"])
        
        # 更新进度百分比
        progress_text = status_row.controls[1]
        progress_text.value = f"{int(task_info['progress'] * 100)}%"
        progress_text.visible = task_info["status"] == "downloading"
        
        # 更新控制按钮
        control_buttons = column.controls[0].controls[1].controls
        pause_button = control_buttons[0]
        cancel_button = control_buttons[1]
        
        # 更新暂停/恢复按钮
        pause_button.icon = ft.icons.PAUSE if task_info["status"] in ["queued", "downloading"] else ft.icons.PLAY_ARROW
        pause_button.tooltip = "暂停" if task_info["status"] in ["queued", "downloading"] else "继续"
        pause_button.visible = task_info["status"] in ["queued", "downloading", "paused"]
        
        # 更新取消按钮可见性
        cancel_button.visible = task_info["status"] in ["queued", "downloading", "paused"]
        
        # 检查是否需要显示空状态
        with self.queue_lock:
            has_tasks = len(self.download_queue) > 0 or len(self.active_downloads) > 0
            self.empty_state.visible = not has_tasks
        
        # 更新UI
        self.page.update()
    
    def process_download_queue(self):
        """处理下载队列"""
        with self.queue_lock:
            # 检查是否有空闲槽位
            if len(self.active_downloads) >= self.max_concurrent_downloads:
                return
            
            # 检查是否有等待的任务
            if not self.download_queue:
                return
            
            # 获取下一个任务
            task_id = self.download_queue[0]
            self.download_queue.pop(0)
            
            # 更新任务状态
            task_info = self.download_tasks[task_id]
            task_info["status"] = "downloading"
            
            # 更新UI
            self.update_task_ui(task_info)
        
        # 启动下载线程
        download_thread = threading.Thread(
            target=self.download_task_thread,
            args=(task_id,),
            daemon=True
        )
        download_thread.start()
        
        # 保存线程引用
        with self.queue_lock:
            self.active_downloads[task_id] = download_thread
    
    def download_task_thread(self, task_id):
        """在线程中下载任务"""
        task_info = self.download_tasks[task_id]
        url = task_info["url"]
        format_id = task_info["format_id"]
        
        try:
            # 获取合适的下载器
            downloader = self.downloader_factory.get_downloader(url)
            if not downloader:
                self.update_task_status(task_id, "failed", "不支持的视频链接")
                return
            
            # 如果是抖音链接，检查是否有 cookies
            if "douyin.com" in url or "iesdouyin.com" in url:
                if not os.path.exists(self.douyin_cookies_file) or os.path.getsize(self.douyin_cookies_file) < 50:
                    self.update_task_status(task_id, "failed", "抖音视频下载需要 cookies，请点击右上角设置按钮配置")
                    return
                
                # 设置 cookies
                downloader.set_cookies_file(self.douyin_cookies_file)
            
            # 创建事件循环
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            
            # 创建取消检查回调
            def progress_callback(progress):
                # 检查任务是否被取消
                with self.queue_lock:
                    if task_id in self.download_tasks and self.download_tasks[task_id]["status"] == "cancelled":
                        return False  # 返回False表示取消下载
                
                # 更新进度
                self.update_task_progress(task_id, progress)
                return True  # 继续下载
            
            # 下载视频
            success, result = loop.run_until_complete(downloader.download_video(
                url, 
                format_id, 
                progress_callback=progress_callback
            ))
            
            if success:
                # 下载成功
                self.update_task_status(task_id, "completed")
                # 显示成功提示
                self.success_snack.content.value = f"视频已下载到: {result}"
                self.success_snack.open = True
                self.page.update()
            else:
                # 下载失败
                self.update_task_status(task_id, "failed", f"下载失败: {result}")
        
        except Exception as e:
            self.update_task_status(task_id, "failed", f"下载过程中出错: {str(e)}")
        
        finally:
            # 从活动下载中移除
            with self.queue_lock:
                if task_id in self.active_downloads:
                    del self.active_downloads[task_id]
            
            # 处理下一个任务
            self.process_download_queue()
    
    def update_task_progress(self, task_id, progress):
        """更新任务进度"""
        with self.queue_lock:
            if task_id in self.download_tasks:
                task_info = self.download_tasks[task_id]
                task_info["progress"] = progress
                self.update_task_ui(task_info)
    
    def update_task_status(self, task_id, status, message=None):
        """更新任务状态"""
        with self.queue_lock:
            if task_id in self.download_tasks:
                task_info = self.download_tasks[task_id]
                task_info["status"] = status
                if message:
                    task_info["message"] = message
                self.update_task_ui(task_info)
                self.save_tasks()  # 保存任务状态
    
    def cancel_download(self, task_id):
        """取消下载任务"""
        with self.queue_lock:
            # 检查任务是否在队列中
            if task_id in self.download_queue:
                # 从队列中移除
                self.download_queue.remove(task_id)
                # 更新状态
                self.update_task_status(task_id, "cancelled")
                return
            
            # 检查任务是否正在下载
            if task_id in self.active_downloads:
                # 标记为取消
                self.update_task_status(task_id, "cancelled")
                # 注意：我们不能直接终止线程，但可以标记状态
                # 实际的线程会在下一次进度更新时检查状态并自行退出
                return

    def load_settings(self):
        """加载设置"""
        try:
            if os.path.exists(self.settings_file):
                with open(self.settings_file, "r", encoding="utf-8") as f:
                    settings = json.loads(f.read())
                    download_dir = settings.get("download_dir", os.path.join(os.path.expanduser("~"), "Downloads", "VideoDownloader"))
            else:
                download_dir = os.path.join(os.path.expanduser("~"), "Downloads", "VideoDownloader")
            
            # 确保下载目录存在并具有正确的权限
            try:
                os.makedirs(download_dir, exist_ok=True)
                # 测试目录是否可写
                test_file = os.path.join(download_dir, ".test")
                with open(test_file, "w") as f:
                    f.write("test")
                os.remove(test_file)
                self.download_dir = download_dir
            except (OSError, IOError) as e:
                print(f"下载目录权限错误: {str(e)}")
                # 如果默认目录不可用，尝试使用临时目录
                self.download_dir = os.path.join(tempfile.gettempdir(), "VideoDownloader")
                os.makedirs(self.download_dir, exist_ok=True)
            
            self.save_settings()
        except Exception as e:
            print(f"加载设置失败: {str(e)}")
            # 使用临时目录作为后备方案
            self.download_dir = os.path.join(tempfile.gettempdir(), "VideoDownloader")
            os.makedirs(self.download_dir, exist_ok=True)
            self.save_settings()
    
    def save_settings(self):
        """保存设置"""
        try:
            settings = {
                "download_dir": self.download_dir
            }
            with open(self.settings_file, "w", encoding="utf-8") as f:
                json.dump(settings, f, ensure_ascii=False, indent=2)
        except Exception as e:
            print(f"保存设置失败: {str(e)}")

    def pick_download_dir(self, e):
        """选择下载目录"""
        def pick_folder_result(e: ft.FilePickerResultEvent):
            if e.path:
                self.download_dir = e.path
                # 更新UI中的路径显示
                download_path_field = e.control.page.get_control("download_path_field")
                if download_path_field:
                    download_path_field.value = e.path
                    download_path_field.update()
                # 保存设置
                self.save_settings()
                # 显示成功提示
                self.success_snack.content.value = "下载路径已更新"
                self.success_snack.open = True
                self.page.update()
        
        # 创建文件选择器
        folder_picker = ft.FilePicker(
            on_result=pick_folder_result,
            allow_multiple=False,
        )
        
        # 添加文件选择器到页面
        self.page.overlay.append(folder_picker)
        self.page.update()
        
        # 打开文件选择器
        folder_picker.get_directory_path()

    def show_error_dialog(self, title, message):
        """显示错误对话框"""
        def close_dlg(e):
            dlg.open = False
            self.page.update()
        
        dlg = AlertDialog(
            title=Text(title),
            content=Text(message),
            actions=[
                TextButton("确定", on_click=close_dlg),
            ],
            actions_alignment=ft.MainAxisAlignment.END,
        )
        
        self.page.dialog = dlg
        dlg.open = True
        self.page.update()

    def load_tasks(self):
        """加载保存的任务"""
        try:
            if os.path.exists(self.tasks_file):
                with open(self.tasks_file, "r", encoding="utf-8") as f:
                    tasks_data = json.loads(f.read())
                    self.download_tasks = tasks_data.get("tasks", {})
                    self.next_task_id = tasks_data.get("next_task_id", 1)
                    
                    # 恢复未完成的任务
                    for task_id, task_info in self.download_tasks.items():
                        if task_info["status"] in ["queued", "downloading", "paused"]:
                            task_info["status"] = "queued"
                            self.download_queue.append(task_id)
        except Exception as e:
            print(f"加载任务失败: {str(e)}")
            self.download_tasks = {}
            self.next_task_id = 1

    def save_tasks(self):
        """保存任务状态"""
        try:
            tasks_data = {
                "tasks": self.download_tasks,
                "next_task_id": self.next_task_id
            }
            with open(self.tasks_file, "w", encoding="utf-8") as f:
                json.dump(tasks_data, f, ensure_ascii=False, indent=2)
        except Exception as e:
            print(f"保存任务失败: {str(e)}")

class DouyinDownloader:
    def __init__(self):
        self.cookies = {}
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
            'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
        }

    def set_cookies_file(self, file_path):
        """设置 cookies 文件路径并解析内容"""
        self.cookies_file = file_path
        self.load_cookies()

    def load_cookies(self):
        """加载 cookies 文件内容"""
        try:
            with open(self.cookies_file, "r", encoding="utf-8") as f:
                cookies_content = f.read().strip()
                if cookies_content.startswith('#'):
                    # 跳过注释行
                    cookies_content = '\n'.join(line for line in cookies_content.split('\n') if not line.startswith('#'))
                
                cookies_list = cookies_content.split(';')
                for cookie in cookies_list:
                    cookie = cookie.strip()
                    if '=' in cookie:
                        name, value = cookie.split('=', 1)
                        name = name.strip()
                        value = value.strip()
                        if name and value:
                            self.cookies[name] = value
            print("已加载 Cookies:", self.cookies)
        except Exception as e:
            print(f"加载 Cookies 失败: {str(e)}")
            raise Exception("无法加载 Cookies 文件，请检查文件格式是否正确")

    async def extract_video_info(self, url):
        """提取抖音视频信息"""
        try:
            # 1. 发送请求获取视频页面
            print(f"正在请求视频页面: {url}")
            response = requests.get(url, headers=self.headers, cookies=self.cookies)
            response.raise_for_status()
            
            # 打印响应内容以便调试
            print("页面响应状态码:", response.status_code)
            print("页面内容片段:", response.text[:500])
            
            # 2. 从页面中提取视频信息
            # 使用正则表达式提取必要的信息
            import re
            
            # 提取标题
            title_pattern = r'<title[^>]*>(.*?)</title>'
            title_match = re.search(title_pattern, response.text)
            title = title_match.group(1) if title_match else "抖音视频"
            
            # 提取视频ID
            video_id_pattern = r'video/(\d+)'
            video_id_match = re.search(video_id_pattern, url)
            video_id = video_id_match.group(1) if video_id_match else None
            
            if not video_id:
                raise Exception("无法提取视频ID")
            
            # 3. 构建API请求获取详细信息
            api_url = f"https://www.douyin.com/aweme/v1/web/aweme/detail/?aweme_id={video_id}"
            api_response = requests.get(api_url, headers=self.headers, cookies=self.cookies)
            api_response.raise_for_status()
            
            # 打印API响应以便调试
            print("API响应:", api_response.text[:500])
            
            video_data = api_response.json()
            
            # 4. 提取视频信息
            video_info = {
                "title": title.replace(" - 抖音", ""),
                "formats": [
                    {
                        "format_id": "hd",
                        "height": 720,
                        "width": 1280,
                        "ext": "mp4",
                        "format_note": "高清",
                        "url": video_data.get("aweme_detail", {}).get("video", {}).get("play_addr", {}).get("url_list", [])[0]
                    },
                    {
                        "format_id": "sd",
                        "height": 480,
                        "width": 854,
                        "ext": "mp4",
                        "format_note": "标清",
                        "url": video_data.get("aweme_detail", {}).get("video", {}).get("play_addr", {}).get("url_list", [])[0]
                    }
                ],
                "cover_url": video_data.get("aweme_detail", {}).get("video", {}).get("cover", {}).get("url_list", [])[0]
            }
            
            print("提取的视频信息:", video_info)
            return video_info
            
        except requests.exceptions.RequestException as e:
            print(f"网络请求失败: {str(e)}")
            raise Exception(f"获取视频信息失败: {str(e)}")
        except Exception as e:
            print(f"解析视频信息时出错: {str(e)}")
            raise Exception(f"解析视频信息失败: {str(e)}")

    async def download_video(self, url, format_id, progress_callback=None):
        """下载视频"""
        try:
            # 1. 获取视频信息
            video_info = await self.extract_video_info(url)
            if not video_info:
                return False, "无法获取视频信息"
            
            # 2. 获取下载地址
            download_url = None
            for fmt in video_info["formats"]:
                if fmt["format_id"] == format_id:
                    download_url = fmt["url"]
                    break
            
            if not download_url:
                return False, "无法获取下载地址"
            
            # 3. 下载视频
            print(f"开始下载视频: {download_url}")
            response = requests.get(download_url, headers=self.headers, cookies=self.cookies, stream=True)
            response.raise_for_status()
            
            # 获取文件大小
            total_size = int(response.headers.get('content-length', 0))
            block_size = 1024  # 1 KB
            downloaded = 0
            
            # 构建保存路径
            import re
            # 清理文件名中的非法字符
            safe_title = re.sub(r'[\\/:*?"<>|]', '_', video_info["title"])
            save_path = os.path.join(os.path.expanduser("~"), "Downloads", "VideoDownloader", f"{safe_title}.mp4")
            
            # 开始下载
            with open(save_path, 'wb') as f:
                for data in response.iter_content(block_size):
                    downloaded += len(data)
                    f.write(data)
                    if progress_callback and total_size:
                        progress = downloaded / total_size
                        if not progress_callback(progress):
                            # 如果回调返回False，表示取消下载
                            return False, "下载已取消"
            
            return True, save_path
            
        except Exception as e:
            print(f"下载视频时出错: {str(e)}")
            return False, f"下载失败: {str(e)}"

class YouTubeDownloader:
    def __init__(self):
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
            'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
        }
        try:
            import yt_dlp
            self.ydl_opts = {
                'quiet': True,
                'no_warnings': True,
                'extract_flat': False,
                'retries': 3,  # 添加重试次数
                'fragment_retries': 3,  # 分片下载重试次数
                'retry_sleep': 5,  # 重试等待时间
                'retry_sleep_functions': {'fragment': lambda n: 5 * (n + 1)},  # 递增重试等待时间
                'socket_timeout': 30,  # 设置超时时间
                'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',  # 优先选择最佳MP4格式
            }
            self.yt_dlp = yt_dlp.YoutubeDL(self.ydl_opts)
        except ImportError:
            raise Exception("请先安装 yt-dlp 库: pip install yt-dlp")

    async def extract_video_info(self, url):
        """提取YouTube视频信息"""
        try:
            print(f"正在解析YouTube视频: {url}")
            
            # 获取视频信息
            info = self.yt_dlp.extract_info(url, download=False)
            if not info:
                raise Exception("无法获取视频信息")
            
            # 构建格式列表
            formats = []
            format_dict = {}
            
            # 首先收集所有可用的格式
            for fmt in info.get('formats', []):
                # 只选择有视频的格式
                if fmt.get('vcodec') != 'none':
                    height = fmt.get('height', 0)
                    width = fmt.get('width', 0)
                    
                    if height and width:
                        quality_key = f"{height}p"
                        if quality_key not in format_dict or fmt.get('tbr', 0) > format_dict[quality_key].get('tbr', 0):
                            format_dict[quality_key] = {
                                "format_id": fmt['format_id'],
                                "height": height,
                                "width": width,
                                "ext": fmt.get('ext', 'mp4'),
                                "format_note": f"{height}p",
                                "url": fmt['url'],
                                "filesize": fmt.get('filesize', 0),
                                "tbr": fmt.get('tbr', 0),  # 视频码率
                                "acodec": fmt.get('acodec', 'none')
                            }
            
            # 将收集到的最佳格式添加到列表中
            for quality, fmt in format_dict.items():
                formats.append(fmt)
            
            # 按分辨率排序
            formats.sort(key=lambda x: x['height'], reverse=True)
            
            if not formats:
                raise Exception("未找到可用的视频格式")
            
            video_info = {
                "title": info.get('title', 'YouTube视频'),
                "formats": formats,
                "cover_url": info.get('thumbnail', '')
            }
            
            print("提取的视频信息:", video_info)
            return video_info
            
        except Exception as e:
            error_msg = str(e)
            if "Video unavailable" in error_msg:
                raise Exception("视频不可用或已被删除")
            elif "Sign in to confirm your age" in error_msg:
                raise Exception("需要登录才能观看此视频")
            elif "Unable to extract uploader id" in error_msg:
                raise Exception("无法获取视频信息，请检查链接是否正确")
            else:
                raise Exception(f"解析YouTube视频失败: {error_msg}")

    async def download_video(self, url, format_id, progress_callback=None):
        """下载YouTube视频"""
        try:
            # 1. 获取视频信息
            video_info = await self.extract_video_info(url)
            if not video_info:
                return False, "无法获取视频信息"
            
            # 2. 获取下载地址
            download_url = None
            selected_format = None
            for fmt in video_info["formats"]:
                if str(fmt["format_id"]) == str(format_id):
                    download_url = fmt["url"]
                    selected_format = fmt
                    break
            
            if not download_url:
                return False, "无法获取下载地址"
            
            # 3. 下载视频
            print(f"开始下载视频: {download_url}")
            response = requests.get(download_url, headers=self.headers, stream=True)
            response.raise_for_status()
            
            # 获取文件大小
            total_size = int(response.headers.get('content-length', 0))
            if total_size == 0 and selected_format:
                total_size = selected_format.get('filesize', 0)
            
            block_size = 1024  # 1 KB
            downloaded = 0
            
            # 构建保存路径
            import re
            # 清理文件名中的非法字符
            safe_title = re.sub(r'[\\/:*?"<>|]', '_', video_info["title"])
            save_path = os.path.join(os.path.expanduser("~"), "Downloads", "VideoDownloader", f"{safe_title}.{selected_format['ext']}")
            
            # 开始下载
            with open(save_path, 'wb') as f:
                for data in response.iter_content(block_size):
                    downloaded += len(data)
                    f.write(data)
                    if progress_callback and total_size:
                        progress = downloaded / total_size
                        if not progress_callback(progress):
                            # 如果回调返回False，表示取消下载
                            return False, "下载已取消"
            
            return True, save_path
            
        except Exception as e:
            print(f"下载视频时出错: {str(e)}")
            return False, f"下载失败: {str(e)}"

class BilibiliDownloader:
    def __init__(self):
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
            'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
            'Referer': 'https://www.bilibili.com',
            'Origin': 'https://www.bilibili.com'
        }
        try:
            import yt_dlp
            self.ydl_opts = {
                'quiet': True,
                'no_warnings': True,
                'extract_flat': False,
                'format': 'bestvideo+bestaudio/best',  # 选择最佳视频和音频质量
                'merge_output_format': 'mp4',  # 合并为mp4格式
                'http_headers': self.headers
            }
            self.yt_dlp = yt_dlp.YoutubeDL(self.ydl_opts)
        except ImportError:
            raise Exception("请先安装 yt-dlp 库: pip install yt-dlp")

    async def extract_video_info(self, url):
        """提取Bilibili视频信息"""
        try:
            print(f"正在解析Bilibili视频: {url}")
            
            # 获取视频信息
            info = self.yt_dlp.extract_info(url, download=False)
            
            # 构建格式列表
            formats = []
            format_dict = {}
            
            # 首先收集所有可用的格式
            for fmt in info.get('formats', []):
                format_id = fmt.get('format_id', '')
                height = fmt.get('height', 0)
                
                # 跳过纯音频格式
                if not height:
                    continue
                
                # 使用清晰度作为键，保存最高码率的版本
                quality_key = f"{height}p"
                if quality_key not in format_dict or fmt.get('tbr', 0) > format_dict[quality_key].get('tbr', 0):
                    format_dict[quality_key] = fmt
            
            # 将收集到的最佳格式添加到列表中
            for quality, fmt in format_dict.items():
                formats.append({
                    "format_id": fmt['format_id'],
                    "height": fmt['height'],
                    "width": fmt.get('width', int(fmt['height'] * 16 / 9)),
                    "ext": "mp4",
                    "format_note": quality,
                    "url": fmt['url'],
                    "filesize": fmt.get('filesize', 0),
                    "tbr": fmt.get('tbr', 0)  # 视频码率
                })
            
            # 按分辨率排序
            formats.sort(key=lambda x: x['height'], reverse=True)
            
            # 获取视频标题和封面
            title = info.get('title', '').replace('_哔哩哔哩_bilibili', '').strip()
            thumbnail = info.get('thumbnail', '')
            
            # 如果没有获取到封面，尝试其他字段
            if not thumbnail:
                thumbnail = info.get('thumbnails', [{}])[0].get('url', '')
            
            video_info = {
                "title": title,
                "formats": formats,
                "cover_url": thumbnail
            }
            
            print("提取的视频信息:", video_info)
            return video_info
            
        except Exception as e:
            print(f"解析Bilibili视频信息时出错: {str(e)}")
            raise Exception(f"解析Bilibili视频失败: {str(e)}")

    async def download_video(self, url, format_id, progress_callback=None):
        """下载Bilibili视频"""
        try:
            # 1. 获取视频信息
            video_info = await self.extract_video_info(url)
            if not video_info:
                return False, "无法获取视频信息"
            
            # 2. 获取下载地址
            download_url = None
            selected_format = None
            for fmt in video_info["formats"]:
                if str(fmt["format_id"]) == str(format_id):
                    download_url = fmt["url"]
                    selected_format = fmt
                    break
            
            if not download_url:
                return False, "无法获取下载地址"
            
            # 3. 下载视频
            print(f"开始下载视频: {download_url}")
            response = requests.get(download_url, headers=self.headers, stream=True)
            response.raise_for_status()
            
            # 获取文件大小
            total_size = int(response.headers.get('content-length', 0))
            if total_size == 0 and selected_format:
                total_size = selected_format.get('filesize', 0)
            
            block_size = 1024  # 1 KB
            downloaded = 0
            
            # 构建保存路径
            import re
            # 清理文件名中的非法字符
            safe_title = re.sub(r'[\\/:*?"<>|]', '_', video_info["title"])
            save_path = os.path.join(os.path.expanduser("~"), "Downloads", "VideoDownloader", f"{safe_title}.mp4")
            
            # 开始下载
            with open(save_path, 'wb') as f:
                for data in response.iter_content(block_size):
                    downloaded += len(data)
                    f.write(data)
                    if progress_callback and total_size:
                        progress = downloaded / total_size
                        if not progress_callback(progress):
                            # 如果回调返回False，表示取消下载
                            return False, "下载已取消"
            
            return True, save_path
            
        except Exception as e:
            print(f"下载视频时出错: {str(e)}")
            return False, f"下载失败: {str(e)}"

class XiaoHongShuDownloader:
    def __init__(self):
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
            'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
            'Referer': 'https://www.xiaohongshu.com'
        }
        try:
            import yt_dlp
            self.ydl_opts = {
                'quiet': True,
                'no_warnings': True,
                'extract_flat': False,
            }
            self.yt_dlp = yt_dlp.YoutubeDL(self.ydl_opts)
        except ImportError:
            raise Exception("请先安装 yt-dlp 库: pip install yt-dlp")

    async def extract_video_info(self, url):
        """提取小红书视频信息"""
        try:
            print(f"正在解析小红书视频: {url}")
            
            # 获取视频信息
            info = self.yt_dlp.extract_info(url, download=False)
            
            # 构建格式列表
            formats = []
            for fmt in info.get('formats', []):
                # 只选择有视频的格式
                if fmt.get('vcodec') != 'none' and fmt.get('acodec') != 'none':
                    height = fmt.get('height', 0)
                    width = fmt.get('width', 0)
                    
                    if height and width:
                        formats.append({
                            "format_id": fmt['format_id'],
                            "height": height,
                            "width": width,
                            "ext": fmt.get('ext', 'mp4'),
                            "format_note": f"{height}p",
                            "url": fmt['url'],
                            "filesize": fmt.get('filesize', 0)
                        })
            
            # 按分辨率排序
            formats.sort(key=lambda x: (x['height'], x['width']), reverse=True)
            
            video_info = {
                "title": info.get('title', '小红书视频'),
                "formats": formats,
                "cover_url": info.get('thumbnail', '')
            }
            
            print("提取的视频信息:", video_info)
            return video_info
            
        except Exception as e:
            print(f"解析小红书视频信息时出错: {str(e)}")
            raise Exception(f"解析小红书视频失败: {str(e)}")

    async def download_video(self, url, format_id, progress_callback=None):
        """下载小红书视频"""
        try:
            # 1. 获取视频信息
            video_info = await self.extract_video_info(url)
            if not video_info:
                return False, "无法获取视频信息"
            
            # 2. 获取下载地址
            download_url = None
            selected_format = None
            for fmt in video_info["formats"]:
                if str(fmt["format_id"]) == str(format_id):
                    download_url = fmt["url"]
                    selected_format = fmt
                    break
            
            if not download_url:
                return False, "无法获取下载地址"
            
            # 3. 下载视频
            print(f"开始下载视频: {download_url}")
            response = requests.get(download_url, headers=self.headers, stream=True)
            response.raise_for_status()
            
            # 获取文件大小
            total_size = int(response.headers.get('content-length', 0))
            if total_size == 0 and selected_format:
                total_size = selected_format.get('filesize', 0)
            
            block_size = 1024  # 1 KB
            downloaded = 0
            
            # 构建保存路径
            import re
            # 清理文件名中的非法字符
            safe_title = re.sub(r'[\\/:*?"<>|]', '_', video_info["title"])
            save_path = os.path.join(os.path.expanduser("~"), "Downloads", "VideoDownloader", f"{safe_title}.{selected_format['ext']}")
            
            # 开始下载
            with open(save_path, 'wb') as f:
                for data in response.iter_content(block_size):
                    downloaded += len(data)
                    f.write(data)
                    if progress_callback and total_size:
                        progress = downloaded / total_size
                        if not progress_callback(progress):
                            # 如果回调返回False，表示取消下载
                            return False, "下载已取消"
            
            return True, save_path
            
        except Exception as e:
            print(f"下载视频时出错: {str(e)}")
            return False, f"下载失败: {str(e)}"

class DownloaderFactory:
    def get_downloader(self, url):
        if "douyin.com" in url or "iesdouyin.com" in url:
            return DouyinDownloader()
        elif "youtube.com" in url or "youtu.be" in url:
            return YouTubeDownloader()
        elif "bilibili.com" in url or "b23.tv" in url:
            return BilibiliDownloader()
        elif "xiaohongshu.com" in url or "xhslink.com" in url:
            return XiaoHongShuDownloader()
        # 其他下载器的逻辑...
        return None

def main():
    app = VideoDownloaderApp()
    # 使用纯Python模式启动应用程序
    ft.app(target=app.main, view=ft.AppView.FLET_APP)

if __name__ == "__main__":
    main() 